package libnetdb.dm.assoc.util;
import java.util.*;

/**
 * A map that map a <code>Class</code> to a value. This class provide a <strong>
 * hierarchical map</strong> mechanism so that a class can map an entry by
 * its hierarchical parents (super class of implemented interfaces). This
 * implementation adapts BFS search to find the closest ancester which has
 * a explicit mapped entry in the table.
 *
 * @author Cheng-Ru Lin
 * @version 1.0
 */

public class ClassMap {
    private HashMap map = new HashMap();

    private class IteratorImpl extends IteratorAdapter{
        private LinkedList list = new LinkedList();

        protected Object findNext(){
            while(!list.isEmpty()){
                Class type = (Class)list.removeFirst();
                if(type.getSuperclass()!=null)
                    list.addLast(type.getSuperclass());
                Class intf[] = type.getInterfaces();
                for(int i=0;i<intf.length;++i)
                    list.addLast(intf[i]);
                if(map.containsKey(type))
                    return new Entry(type, map.get(type));
            }
            return null;
        }

        public IteratorImpl(Class type){
            list.addLast(type);
            value = findNext();
        }
    }

    /**
     * A class map entry (type-value pair). The <code>ClassMap.getEntry(Class)</code>
     * method returns a instance of this class. These <code>ClassMap.Entry</code> objects
     * are valid only before any modification to the ClassMap excepts the change is done
     * by the operation of this class.
     */
    public class Entry{
        private Class type;
        private Object value;

        private Entry(Class type, Object value){
            this.type = type;
            this.value = value;
        }

        /**
         * Gets the key which is a <code>Class</code> of this entry.
         * @return the key of the entry
         */
        public Class getType(){
            return type;
        }

        /**
         * Gets the value of this entry.
         * @return the value of the entry
         */
        public Object getValue(){
            return value;
        }

        /**
         * Sets the value of this entry.
         * @param value the value of the entry
         */
        public void setValue(Object value){
            map.put(type, value);
        }
    }

    /**
     * Constructs a new empty <code>ClassMap</code>
     */
    public ClassMap() {}

    /**
     * Returns the value to which this map hierarchically maps the specified type.
     * Returns null if the map contains no mapping for this key. A return value of null
     * does not necessarily indicate that the map contains no mapping for the key;
     * it's also possible that the map explicitly maps the key to null.
     * The <code>getEntry()</code> operation may be used to distinguish these two cases.
     *
     * @param type the key which the returned object is associated with
     * @return the value to which this map maps the specified type, or null if the map
     *         contains no mapping for this key.
     * @see #getEntry(Class)
     */
    public Object get(Class type){
        Entry entry = getEntry(type);
        return entry == null ? null : entry.getValue();
    }

    /**
     * Returns the entry to which this map hierarchically maps the specified type. A
     * <code>Entry</code> is a class contains the explicitly mapped type-value pair.
     * A return value of null indicate that the map contains no mapping for this type.
     *
     * @param type the key which the returned entry is associated with
     * @return the entry to which this map maps the specified type, or null if the map
     *         contains no mapping for this key.
     * @see Entry
     */
    public Entry getEntry(Class type){
        LinkedList list = new LinkedList();
        if(type!=null) list.addLast(type);
        while(!list.isEmpty()){
            type = (Class) list.removeFirst();
            if(map.containsKey(type))
                return new Entry(type, map.get(type));
            if(type.getSuperclass()!=null) list.addLast(type.getSuperclass());
            Class intf[] = type.getInterfaces();
            for(int i=0;i<intf.length;++i) list.addLast(intf[i]);
        }
        return null;
    }

    /**
     * Returns an iterator over the elements in this map that cound be hierarchical mapped
     * for the specified key. The elements are returned in BFS order of the hierarchical
     * structure.
     *
     * @param type the key for which the returned iterator is associated with
     * @return an iterator view of all assocated elements
     */
    public Iterator entryIterator(Class type){
        return new IteratorImpl(type);
    }

    /**
     * Associates the specified value with the specified type in this map.
     * If the map previously contained a mapping for this key, the old value is replaced
     * by the specified value.
     *
     * @param type key with which the specified value is to be associated.
     * @param value value to be associated with the specified key.
     */
    public void put(Class type, Object value){
        map.put(type, value);
    }

    /**
     * Returns if a entry coresponding to type is in this ClassMap.
     *
     * @param type key to test the existence of entry.
     * @return true if this map contains the key type.
     */
    public boolean containsClass(Class type){
        return map.containsKey(type);
    }

    /**
     * Returns the value to which this map directly maps the specified type.
     * Returns null if the map contains no mapping for this key. A return value of null
     * does not necessarily indicate that the map contains no mapping for the key;
     * it's also possible that the map explicitly maps the key to null.
     * The <code>getEntry()</code> operation may be used to distinguish these two cases.
     *
     * @param type the key for which the returned object is associated with
     * @return the value to which this map maps the specified type, or null if the map
     *         contains no mapping for this key.
     * @see #getEntry(Class)
     * @see #get(Class)
     */
    public Object getOriginal(Class type){
        return map.get(type);
    }

    /**
     * Removes the mapping for this type from this map if it is present. More formally,
     * the mapping should be explicitly built by the <code>put(Class, Object)</code> method.
     * Do nothing if there is no such type in this map.
     * @param type whose mapping is to be removed from the map.
     */
    public void remove(Class type){
        map.remove(type);
    }

    /**
     * Returns the number of explicit type-value mappings in this map. If the map contains
     * more than Integer.MAX_VALUE elements, returns Integer.MAX_VALUE.
     *
     * @return the number of type-value mappings in this map.
     */
    public int size(){
        return map.size();
    }

    /**
     * Returns a collection view of the values contained in this map. The collection is
     * backed by the map, so changes to the map are reflected in the collection, and
     * vice-versa. If the map is modified while an iteration over the collection is
     * in progress, the results of the iteration are undefined. The collection supports
     * element removal, which removes the corresponding mapping from the map,
     * via the Iterator.remove, Collection.remove, removeAll, retainAll and clear
     * operations. It does not support the add or addAll operations.
     *
     * @return a collection view of the values contained in this map.
     */
    public Collection values(){
        return map.values();
    }

}